// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Xml.Linq;

namespace SL.IdentityModel.Protocols.WSTrust
{
    /// <summary>
    /// Class serializes XML from the response message in to the RequestSecurityTokenRespnse
    /// </summary>
    internal class WSTrust13ResponseSerializer
    {
        private const string Address = "Address";
        private const string AppliesTo = "AppliesTo";
        private const string Created = "Created";
        private const string EndpointReference = "EndpointReference";
        private const string Expires = "Expires";
        private const string Lifetime = "Lifetime";
        private XNamespace nsAddressing = "http://www.w3.org/2005/08/addressing";
        private XNamespace nsSecurityPolicy = "http://schemas.xmlsoap.org/ws/2004/09/policy";
        private XNamespace nsUtility = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";

        /// <summary>
        /// Create as instance of the class by reading the response message
        /// </summary>
        /// <param name="messageResponse"></param>
        /// <returns></returns>
        public RequestSecurityTokenResponse ReadXml( Message messageResponse )
        {
            if ( null == messageResponse )
            {
                throw new ArgumentNullException( "messageResponse is null" );
            }

            XDocument rstr = XDocument.Parse( messageResponse.ToString() );
            RequestSecurityTokenResponse response = new RequestSecurityTokenResponse();

            response.RequestedSecurityToken = ReadRequestedSecurityToken( rstr );
            response.AppliesTo = ReadAppliesTo( rstr );
            response.Created = ReadLifeTimeHelper( rstr, Created );
            response.Expires = ReadLifeTimeHelper( rstr, Expires );
            response.ProofTokenReference = ReadProofTokenReference( rstr );
            response.EntropyBytes = ReadEntropy( rstr );
            response.KeySizeInBits = ReadKeySize( rstr );
            response.KeyType = ReadKeyType( rstr );
            response.ComputedKeyAlgorithm = ReadComputedKeyAlgorithm( rstr );
            response.ProtectedKeyBytes = ReadProtectedKey( rstr );

            return response;
        }

        /// <summary>
        /// Reads AppliesTo element from the response
        /// </summary>
        /// <param name="rstr">RequestSecurityTokenResponse message XML</param>
        /// <returns>AppliesTo EndpointAddress</returns>
        private EndpointAddress ReadAppliesTo( XDocument rstr )
        {
            string appliesTo = ( from r in rstr.Descendants( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse )
                                     .Elements( nsSecurityPolicy + AppliesTo )
                                     .Elements( nsAddressing + EndpointReference )
                                     .Elements( nsAddressing + Address )
                                 select r.Value ).FirstOrDefault<string>();

            if ( null == appliesTo )
            {
                return null;
            }

            return new EndpointAddress( appliesTo );
        }

        /// <summary>
        /// Reads ComputedKey element from the response
        /// </summary>
        /// <param name="rstr">RequestSecurityTokenResponse message XML</param>
        /// <returns>ComputedKey string</returns>
        private string ReadComputedKeyAlgorithm( XDocument rstr )
        {
            string computedKey = ( from r in rstr.Descendants( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse )
                                         .Elements( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestedProofToken )
                                         .Elements( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.ComputedKey )
                                   select r.Value ).FirstOrDefault<string>();

            if ( string.IsNullOrEmpty( computedKey ) )
            {
                return null;
            }

            return computedKey;
        }

        /// <summary>
        /// Reads BinarySecret element from the response
        /// </summary>
        /// <param name="rstr">RequestSecurityTokenResponse message XML</param>
        /// <returns>ServerEntropy byte[]</returns>
        private byte[] ReadEntropy( XDocument rstr )
        {
            string entropyString = ( from r in rstr.Descendants( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse )
                                         .Elements( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.Entropy )
                                         .Elements( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.BinarySecret )
                                     select r.Value ).FirstOrDefault<string>();

            if ( String.IsNullOrEmpty( entropyString ) )
            {
                return null;
            }

            return Convert.FromBase64String( entropyString );
        }

        /// <summary>
        /// Reads KeySize element from the response
        /// </summary>
        /// <param name="rstr">RequestSecurityTokenResponse message XML</param>
        /// <returns>KeySize int?</returns>
        private int? ReadKeySize( XDocument rstr )
        {
            string keySizeString = ( from r in rstr.Descendants( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse )
                                         .Elements( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.KeySize )
                                     select r.Value ).FirstOrDefault<string>();

            if ( String.IsNullOrEmpty( keySizeString ) )
            {
                return null;
            }

            return Convert.ToInt32( keySizeString );
        }

        /// <summary>
        /// Reads KeyType element from the response
        /// </summary>
        /// <param name="rstr">RequestSecurityTokenResponse message XML</param>
        /// <returns>KeyType string</returns>
        private string ReadKeyType( XDocument rstr )
        {
            string keyType = ( from r in rstr.Descendants( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse )
                                         .Elements( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.KeyType )
                               select r.Value ).FirstOrDefault<string>();

            return keyType;
        }

        /// <summary>
        /// Reads LifeTime element from the RequestSecurityTokenResponse XML
        /// </summary>
        /// <param name="rstr">RequestSecurityTokenResponse message XML</param>
        /// <param name="elementName">Created or Expires, specifies element being read</param>
        /// <returns></returns>
        private DateTime ReadLifeTimeHelper( XDocument rstr, string elementName )
        {
            DateTime tempDateTime;
            string time;

            time = ( from r in rstr.Descendants( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse )
                                  .Elements( WSTrust13Constants.NamespaceURI + Lifetime )
                                  .Elements( nsUtility + elementName )
                     select r.Value ).FirstOrDefault<string>() ?? String.Empty;

            if ( DateTime.TryParse( time, out tempDateTime ) )
            {
                return tempDateTime;
            }
            else
            {
                throw new ArgumentException( string.Format( "bad datetime value for {0}", elementName ) );
            }
        }

        /// <summary>
        /// Reads requested security token
        /// </summary>
        /// <param name="rstr">RequestSecurityTokenResponse message XML</param>
        /// <returns>RequestedSecurityToken</returns>
        private RequestedSecurityToken ReadRequestedSecurityToken( XDocument rstr )
        {
            XElement requestedToken = ( from rt in rstr.Descendants( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestedSecurityToken )
                                        select rt ).FirstOrDefault<XElement>();

            if ( null == requestedToken )
            {
                throw new ArgumentNullException( "No RequestedSecurityToken in RequestSecurityTokenResponse" );
            }

            XNode token = requestedToken.DescendantNodes().FirstOrDefault();

            if ( null == token )
            {
                throw new ArgumentNullException( "RequestedSecurityToken did not contain a token" );
            }

            return new RequestedSecurityToken( token.ToString( SaveOptions.DisableFormatting ) );
        }

        /// <summary>
        /// Reads SecurityTokenReference element from the response
        /// </summary>
        /// <param name="rstr">RequestSecurityTokenResponse message XML</param>
        /// <returns>SecurityTokenReference XNode</returns>
        private XNode ReadProofTokenReference( XDocument rstr )
        {
            XElement attachedReference = ( from rt in rstr.Descendants( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse )
                                           .Elements( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestedAttachedReference )
                                           select rt ).FirstOrDefault<XElement>();

            if ( null == attachedReference )
            {
                return null;
            }

            return attachedReference.DescendantNodes().FirstOrDefault();
        }

        /// <summary>
        /// Reads BinarySecret element from inside the RequestedProofToken node in the response
        /// </summary>
        /// <param name="rstr">RequestSecurityTokenResponse message XML</param>
        /// <returns>ProtectedKey byte[]</returns>
        private byte[] ReadProtectedKey( XDocument rstr )
        {
            string protectedKeyString = ( from r in rstr.Descendants( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse )
                                              .Elements( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.RequestedProofToken )
                                              .Elements( WSTrust13Constants.NamespaceURI + WSTrust13Constants.ElementNames.BinarySecret )
                                          select r.Value ).FirstOrDefault<string>();

            if ( string.IsNullOrEmpty( protectedKeyString ) )
            {
                return null;
            }

            return Convert.FromBase64String( protectedKeyString );
        }


    }
}